%% movfun.m
%  Function by adq@XJTU
%  将一维数组 x 划分出滑窗后分别应用函数句柄 func.
%  A = movfun(func, x, n_slide, n_overlap, padding, pAlign)
% 
%  单个滑窗长度为 n_slide; 滑窗之间有长度为 n_overlap 的重叠, 0 为禁用重叠. 输出行向量 A 为函数返回值的顺序拼接. 首定义为数组的 1 下标位置, 尾则是最大下标位置.
%  func 应当为函数句柄 function_handle. 文件/内联函数需使用 @fcnName 格式输入, 构建相应的函数句柄.
%  padding 填充选项
%  指定为 'none', 首/尾滑窗长度可能短于 n_slide.
%  指定为 'repeat' 或一个数(包括 NaN), 则每个滑窗长度都为 n_slide, 并在短滑窗处用最后一个有效值或指定值填充.
%  默认为 'none'.
%  
%  pAlign 对齐选项
%  如果 pAlign 为 'l' / 'left', 滑窗向左(首部)对齐, 在尾端出现可能的补 0 或短滑窗. 此时, 函数先应用于左侧滑窗.
%  否则, 滑窗向右(尾部)对齐, 在首端出现可能的补 0 或短滑窗. 此时, 函数先应用于右侧滑窗.
%  默认为右侧对齐. 注意, 如果 func 是时变的, 就必须关注执行顺序.
%  
%  Comment:
%  使用尽可能少的滑窗填充, 因而对于 n_append = 1 这样的滤波应用场景, 输出数据维度和输入数据维度不同,
%  操作上使用向右对齐, 在左侧重复填充首个有效值.
%  当前的架构适用于如频谱峰值检索, 尽可能少的引入.
%  输出使用元胞数组, 便于 func 传递出更多的参数, 对频谱寻峰应用有利但不利于滤波作图.
%  
%  History
%  1 Jan. 2022
%  增加 padding 填充选项, 允许不填充 / 指定填充 / 重复填充;
%  修改 pAlign 参数类型;增加错误参数提示;
%  修改输出类型为行向量;
%  修复 slide_count 为 1 时向函数传递空数组的错误;
%  修改帮助文件格式.
%  31 Dec. 2021
%  创建并实现基础功能. 使用 assert 手动完成参数验证. 无命令解析, 所有参数必须在对应位置指定.
function A = movfun(func, x, n_slide, n_overlap, padding, pAlign)

	n_data = length(x);
	n_append = n_slide - n_overlap; % 每次滑窗新增的数据数量
	
	assert(isvector(x), '必须使用数组, 但输入了 %s %s.', num2str(size(x)), class(x));
	assert((n_data >= n_slide), '数组长度 %d 应大于滑窗长度 %d.', n_data, n_slide);
	assert((n_append > 0 && n_overlap >= 0), '滑窗长度 %d 应大于重叠区域长度 %d.', n_slide, n_overlap);
	
	slide_count = ceil((n_data - n_slide) / n_append) + 1;
	
	A = zeros(1, slide_count);
	
	if (pAlign(1) == 'l')
		%% 左侧对齐		
		for slide_idx = 1 : slide_count - 1
			bias = n_slide - n_overlap + (slide_idx - 2) * n_append + 1;
			A(slide_idx) = func(x(bias : bias + n_slide - 1));
		end
		
		% 如果 slide_count == 1, slide_idx = 1:0 将会把它变成空数组, 引发错误
		slide_idx = slide_count;
		bias = n_slide - n_overlap + (slide_idx - 2) * n_append + 1;
		
		if (isnumeric(padding))
			A(slide_idx) = func([x(bias : end), padding .* ones(1, n_slide-(n_data-bias+1))]);
		elseif (padding(1) == 'r') % 'repeat'
			A(slide_idx) = func([x(bias : end), x(end) .* ones(1, n_slide-(n_data-bias+1))]);			
		else % 'none'
			A(slide_idx) = func(x(bias : end));
		end

	else
		%% 右侧对齐		
		for slide_idx = slide_count : -1 : 2
			bias = n_data - n_slide - (slide_count - slide_idx) * n_append + 1;
			A(slide_idx) = func(x(bias : bias + n_slide - 1));
		end
		
		slide_idx = 1;
		bias = n_data - n_slide - (slide_count - slide_idx) * n_append + 1;
		bias_r = bias + n_slide - 1;

		if (isnumeric(padding))
			A(slide_idx) = func([padding .* ones(1, n_slide-bias_r), x(1 : bias_r)]);
		elseif (padding(1) == 'r') % 'repeat'
			A(slide_idx) = func([x(1) .* ones(1, n_slide-bias_r), x(1 : bias_r)]);
		else % 'none'
			A(slide_idx) = func(x(1 : bias_r));
		end
		
	end
	
end